#ifndef MODEL_H_INCLUDED
#define MODEL_H_INCLUDED

#include <vector>
#include <map>
#include <string>
#include <stdexcept>
#include <cassert>

#include "../math/vec3.h"
#include "../math/vec2.h"
#include "../math/vec4.h"

using std::vector;
using std::map;
using std::string;

namespace sheeplib {

    namespace model {

		typedef unsigned int SLuint;

		struct ModelTriangle {
			slVec3 			m_Normal;
			slVec2 			m_TexCoords[3];
			SLuint 			m_MaterialID;
		};

		struct ModelMaterial {
			slVec4	m_Ambient;
            slVec4	m_Diffuse;
            slVec4	m_Specular;
            slVec4	m_Emissive;
            float	m_Shininess;
            float 	m_Transparency;
            
            string 	m_TextureFile1;
            string  m_TextureFile2;
		};

		typedef const vector<ModelMaterial> slMaterialList;
		
		class TriangleGroup {
			private:
				vector<unsigned int> m_Indices;
				vector<ModelTriangle> m_Attributes;

			public:
				void GetTriangleIndices(int at, int& idx1, int& idx2, int& idx3) {
					idx1 = m_Indices[at * 3];
					idx2 = m_Indices[at * 3 + 1];
					idx3 = m_Indices[at * 3 + 2];
				}

				void GetTriangleTexCoords(int at, slVec2& v1, slVec2& v2, slVec2& v3) {
					v1 = m_Attributes[at].m_TexCoords[0];
					v2 = m_Attributes[at].m_TexCoords[1];
					v3 = m_Attributes[at].m_TexCoords[2];
				}

				ModelTriangle& GetTriangleAttr(int at) {
					return m_Attributes[at];
				}

				vector<ModelTriangle>& GetTriangleAttributes() {
					return m_Attributes;
				}

				slVec3& GetTriangleNormal(int at) {
					return m_Attributes[at].m_Normal;
				}

				vector<unsigned int>& GetIndices() {
					return m_Indices;
				}

				const vector<unsigned int>& GetIndices() const {
					return m_Indices;
				}
				
				unsigned int* GetIndexPtr() {
					return &m_Indices[0];
				}
				
				const unsigned int* GetIndexPtr() const {
					return &m_Indices[0];
				}
		};

		class ModelFrame {
			private:
				map<unsigned int, TriangleGroup> m_Groups;
				vector<slVec3> m_Vertices;
				vector<slVec2> m_TexCoords;
				vector<int>  m_Bones;
				vector<slVec3> m_Normals; //Vertex normals
				vector<slVec3> m_Tangents; //Vertex tangents
			public:
                slVec3* GetVertexPtr() {
                	assert(!m_Vertices.empty());
                    return &m_Vertices[0];
                }
                
                const slVec3* GetVertexPtr() const {
                	assert(!m_Vertices.empty());
					return &m_Vertices[0];
                }

				vector<slVec3>& GetVertices() {
					return m_Vertices;
				}
				
                slVec2* GetTexCoordPtr() {
                	assert(!m_TexCoords.empty());
                    return &m_TexCoords[0];
                }
                
                const slVec2* GetTexCoordPtr() const {
                	assert(!m_TexCoords.empty());
					return &m_TexCoords[0];
                }

				vector<slVec2>& GetTexCoords() {
					return m_TexCoords;
				}
				
				vector<slVec3>& GetNormals() {
					return m_Normals;
				}

                slVec3* GetNormalPtr() {
					return &m_Normals[0];
                }

				const slVec3* GetNormalPtr() const {
					return &m_Normals[0];
				}
				
				unsigned int GetNumVertices() {
                    return static_cast<unsigned int> (m_Vertices.size());
                }

                int GetBoneID(int vertexID) {
					return m_Bones[vertexID];
                }

				vector<int>& GetBoneIDs() {
					return m_Bones;
				}

                TriangleGroup& GetGroup(unsigned int at) {
					return m_Groups[at];
                }

                const TriangleGroup& GetGroup(unsigned int at) const {
					return GetGroups().at(at);
                }
                
                map<unsigned int, TriangleGroup>& GetGroups() {
					return m_Groups;
                }

                const map<unsigned int, TriangleGroup>& GetGroups() const {
					return m_Groups;
                }	         
		};

		struct ModelInterface {
			virtual ~ModelInterface() {}

			virtual void slBasicRender() const = 0;
			virtual void slRenderSubset(unsigned int i) const = 0;
			
			virtual bool slLoadModel(const string& filename) = 0;
			virtual bool slLoadAnimationFile(const string& filename) = 0;
			
			virtual void slUpdateAnimation(float dT) = 0;
			
			virtual const vector<string> slListAnimations() const = 0;
			virtual void slSetNextAnimation(const string& animationName) = 0;
			
			//virtual void slDestroyModel() = 0;
			virtual const string slGetError() const = 0;
			virtual bool slHasError() const = 0;
		};

        class Model : public ModelInterface {
            private:
                vector<ModelFrame> 		m_Frames;
				vector<ModelMaterial> 	m_Materials; //TriangleGroup(i) == Material(i)
				
				mutable string m_ErrorString; //All variables are PRIVATE the best encapsulation you can get
			protected:
				const string GetErrorString() const {
                	string s = m_ErrorString;
                	m_ErrorString.clear();
					return s;
                }
                		
				bool HasError() const {
					return !m_ErrorString.empty();
				}
				
                void SetErrorString(const string& s) {
					m_ErrorString = s;
                }

                vector<ModelFrame>& GetFrames() {
					return m_Frames;
                }

                ModelFrame& GetFrame(int at) {
					return m_Frames[at];
                }
                
                const ModelFrame& GetFrame(int at) const {
					return m_Frames[at];
                }
                                
				vector<ModelMaterial>& GetMaterials() {
					return m_Materials;
				}
				
                virtual bool LoadModelFile(const string& filename) = 0;
                virtual void BasicGLRender() const = 0;
                virtual void Update(float dT) = 0;
							
            public:
                Model() {

                }

                virtual ~Model() {
                    m_Frames.clear();
                }

				//Public API functions
				inline const string slGetError() const {
					return GetErrorString();
				}
				
				inline bool slHasError() const {
					return HasError();
				}
				
				inline bool slLoadModel(const string& filename) {
					return LoadModelFile(filename);
				}
				
				inline void slUpdateAnimation(float dT) {
					Update(dT);
				}
				
				virtual bool slLoadAnimationFile(const string& filename) {
					throw std::logic_error("Not implemented. This may be a static model!");	
				}
				
				virtual void slSetNextAnimation(const string& animationName) {
					throw std::logic_error("Not implemented. This may be a static model!");	
				}
				
				slMaterialList& slGetMaterials() const {
					return m_Materials;
				}
				
				virtual const vector<string> slListAnimations() const {
					throw std::logic_error("Not implemented. This may be a static model!");	
				}
        };
    }
}

#endif // MODEL_H_INCLUDED
